home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
C/C++ Interactive Reference Guide
/
C-C++ Interactive Reference Guide.iso
/
c_ref
/
csource5
/
357_01
/
cstar1.exe
/
DEF.C
< prev
next >
Wrap
C/C++ Source or Header
|
1991-06-18
|
14KB
|
727 lines
/*
C* -- Macro definition and expansion routines.
source: def.c
started: October 22, 1985
version:
January 6, 1987
March 7, 1989
PUBLIC DOMAIN SOFTWARE
The CSTAR program was placed in the public domain on June 15, 1991,
by its author and sole owner,
Edward K. Ream
1617 Monroe Street
Madison, WI 53711
(608) 257-0802
CSTAR may be used for any commercial or non-commercial purpose.
See cstar.h or cstar.c for a DISCLAIMER OF WARRANTIES.
*/
#include "cstar.h"
/*
Visible routines defined in this file:
*/
void pp_def (void);
void pp_expand (int nargs, unsigned char * rtext);
/*
Internal routines:
*/
static void t_aalist (int nargs);
static void t_1aarg (void);
/*
These routines do NOT use dynamic storage allocation to keep track of
actual or formal parameters. They are kept in the following array.
*/
#define MAX_PARAM 4000
static unsigned char param [MAX_PARAM];
static unsigned char * param_p;
static int param_c;
#define MAX_ARG 100
static unsigned char * arg [MAX_ARG];
static int arg_c;
/*
Space for replacement text.
Must be separate from param [].
WARNING: MAX_RTEXT1 + MAX_SYMBOL must be <= MAX_RTEXT
*/
#define MAX_RTEXT 2000
#define MAX_RTEXT1 1000
static unsigned char rtext [MAX_RTEXT];
/*
The pp_def() routine replaces formal param n by n+ARG_OFFSET, ARG_FLAG.
The pp_expand() routine replaces these two characters by
the n'th actual parameter.
The ARG_FLAG character should be a character that can never appear in
normal text, but it MUST NOT BE NEGATIVE, so as to fit in a char.
*/
#define ARG_FLAG 1
#define ARG_OFFSET '0'
/*
Handle the #define directive by parsing the statement, and entering
the macro's name, number of arguments and replacement text into the
macro table.
Formal arguments are found and replaced by a flag byte followed by the
number of the formal argument.
*/
void
pp_def(void)
{
register int i;
register unsigned char * textp;
register int textc;
TICK("pp_def");
/* Initialize. */
textp = ¶m[0];
textc = 0;
arg_c = 0;
/* Make sure the name is present. */
if (!isid1(ch)) {
t_error("#define ignored -- no symbol name given.");
skip_1line();
return;
}
/* Put the name at the start of param[].*/
while (isid2(ch)) {
*textp++ = ch;
textc++;
sysnext();
}
*textp++ = '\0';
textc++;
if (ch != '(') {
/* Indicate no argument list and continue. */
arg_c = -1;
goto gettext;
}
else {
sysnext();
}
if (ch == ')') {
sysnext();
arg_c = 0;
goto gettext;
}
/*
Put the formal arguments into param[].
Set pointers to the arguments in arg[].
*/
for (arg_c = 0; arg_c < MAX_ARG; ) {
TICK("pp_def2");
skip_bl();
if (!isid1(ch)) {
t_error(
"#define ignored -- formal arg must be an identifier"
);
skip_1line();
return;
}
/* Point arg[] at the start of the parameter. */
arg [arg_c++] = textp;
/* Copy one formal arg to param[]. */
while (isid2(ch)) {
*textp++ = ch;
textc++;
sysnext();
}
*textp++ = '\0';
textc++;
if (textc >= MAX_PARAM) {
t_error("formal parameter list too long");
skip_1line();
return;
}
if (ch == ')') {
sysnext();
goto gettext;
}
else if (ch == ',') {
sysnext();
}
}
t_error("#define ignored -- too many arguments.");
skip_1line();
return;
/*
At this point, arg_c contains the number of formal arguments, or
-1 if no argument list was given. 0 is allowed and means that the
argument list was ().
*/
gettext:
TICK("pp_def3");
skip_bl();
/*
Put the replacement text into rtext[].
Replace formal arg n by n FLAG on the fly.
*/
textp = &rtext[0];
textc = 0;
for(;;) {
TICK("pp_def4");
switch (ch) {
case END_FILE:
goto done;
case '\n':
/* Let the main parsing loop handle the newline. */
goto done;
case '\r':
sysnext();
continue;
case '\\':
sysnext();
if (skip_crlf()) {
/* Allow continuation of definitions. */
/* Do NOT put newline into replacement text. */
/* Thus, PP direcives may NOT be recognized */
/* from within macro expansions (4/8/86). */
*textp++ = ' ';
textc++;
do_nl();
begin_line(FALSE);
}
else {
*textp++ = '\\';
textc++;
}
continue;
case '/':
/* Eliminate comments */
sysnext();
if (ch != '*') {
*textp++ = '/';
textc++;
continue;
}
for (;;) {
if (ch == END_FILE ||
ch == '\n' ||
ch == '\r') {
t_error
("bad comment in macro definition");
break;
}
else if (ch == '*') {
sysnext();
if (ch == '/') {
sysnext();
break;
}
}
else {
sysnext();
}
}
continue;
default:
TICK("pp_def5");
if (!isid1(ch)) {
*textp++ = ch;
textc++;
sysnext();
continue;
}
/* Copy the id into the buffer and set t_length. */
t_id(textp);
/* See if the id is a formal arg. */
for (i = 0; i < arg_c ; i++) {
TICK("pp_def6");
if (str_eq(textp, arg[i])) {
*textp++ = (unsigned char)i + ARG_OFFSET;
*textp++ = ARG_FLAG;
textc += 2;
break;
}
}
if (i == arg_c || arg_c == -1) {
/* Not found. Move past the identifier. */
textp += t_length;
textc += t_length;
}
continue;
}
}
done:
TICK("pp_def7");
/* This check is made only here to save a little time. */
if (textc >= MAX_RTEXT) {
fatal("#define too long...");
}
/* Strip white space off end of definition. */
do {
textc--;
textp--;
}
while (textc > 0 && (*textp == ' ' || *textp == '\t'));
textp++;
textc++;
*textp = '\0';
/*
Enter the symbol, replacement text and number of arguments
into the macro table.
*/
(void) mst_enter(param, rtext, arg_c);
}
/*
Expand a macro.
Step one: put the actual parameters into param[].
Step two: push the replacement text, replacing
formal parameter flags by actual params on the fly.
*/
void
pp_expand (register int nargs, register unsigned char * rtext)
{
register int i;
register unsigned char c, * textp;
register int textc;
TICK("pp_expand");
TRACE("pp_expand",
printf("pp_expand(nargs: %d, rtext: %s)\n", nargs, rtext));
TRACE("pp_expand", printf("ch = %c\n",ch));
/* Make no argument substitutions if none possible. */
if (nargs < 0) {
TICK("pp_expand1");
/* No arguments in replacement text. */
syspush(ch);
sysspush(rtext);
sysnext();
return;
}
TICK("pp_expand1");
/*
Step 1:
Put the actual parameters in param[].
Put pointers to parameters in arg[].
*/
t_aalist(nargs);
/* Save the current character. */
syspush(ch);
ch = ' ';
/*
Step 2:
Push back the replacement stack.
Replace n ARG_FLAG by actual argument n.
*/
textc = str_len(rtext);
textp = rtext;
textp += textc;
while (textc > 0) {
c = *--textp;
textc--;
TICK("pp_expand1");
if (c == ARG_FLAG) {
c = *--textp;
c -= ARG_OFFSET;
textc--;
if ((int)c >= nargs) {
fatal("pp_expand: can't happen");
}
TICK("pp_expand_arg");
sysspush(arg [c]);
continue;
}
else {
TICK("pp_expand_char");
syspush(c);
}
}
/* Get the next character into ch. */
sysnext();
}
/*
Parse a list of nargs actual arguments.
Put the arguments into param[].
Put pointers to each argument in arg[].
*/
static int call_start;
static void
t_aalist(register int nargs)
{
char msg [100];
char line [10];
TICK("t_aalist");
/* Initialize. */
arg_c = 0;
param_p = ¶m[0];
param_c = 0;
/* Save starting line number of the macro call. */
call_start = t_line;
/* Look for opening parenthesis on the same line. */
skip_bl();
if (ch != '(') {
t_error("Missing arguments to macro call--nulls assumed.");
goto check1;
}
else {
sysnext();
}
if (ch == ')') {
sysnext();
arg_c = 0;
goto check;
}
for(;;) {
TICK("t_aalist1");
t_1aarg();
if (ch == END_FILE) {
goto check1;
}
else if (ch == ')') {
sysnext();
break;
}
else if (ch == ',') {
sysnext();
}
else {
/* Error detected in t_1aalist(). */
break;
}
}
check:
if (arg_c != nargs) {
if (call_start != t_line) {
strcpy(msg, "Macro call starting at line ");
conv2s(call_start, line);
strcat(msg, line);
strcat(msg, " requires ");
}
else {
strcpy(msg, "Macro call requires ");
}
conv2s(nargs, line);
strcat(msg, line);
strcat(msg, " arguments.");
t_error(msg);
}
/* 4/8/86 */
check1:
while (arg_c < nargs) {
/* Clear out the missing arguments. */
arg [arg_c++] = NULL;
}
}
/*
Copy the next actual argument into param[].
On entry, param_p and param_c describe the state of param[].
Point arg [arg_c] at the the parameter.
Formal arguments are simply identifiers, which are handled by t_id(),
but actual arguments are LISTS of tokens separated by commas. As an
added twist, commas inside single or double quotes, or commas which are
"protected" by additional parentheses do NOT separate actual args.
Thus, each of the following calls have to M have ONE actual argument:
M(a)
M(a * N(c,b))
M(',')
M("a,b")
M((a,b))
M((a,")",b))
There is one place where the exact details about how whitespace is
treated DOES make a difference, namely involving whitespace in actual
parameters to macros. The reason is that actual arguments are replaced
EVEN INSIDE STRINGS. If the string appears in a printf() statement,
for instance, what gets printed depends on just exactly how whitespace
is treated. The C Refence Guide is silent on this point.
This routine changes comments to one blank and retains all other white
space as is. Thus, tokens will always be properly surrounded by white
space when they need to be.
*/
static void
t_1aarg(void)
{
register unsigned char * textp;
register int textc;
register int plevel;
register unsigned char delim;
char buffer [100];
char linebuf [10];
TICK("t_1aarg");
/* Allow raw newlines only at the beginning or end of arguments. */
/* comment out -----
skip_ws();
if (ch == '\r') {
sysnext();
}
if (ch == '\n') {
do_nl();
sysnext();
}
----- end comment out */
/* No parens have been seen yet. */
plevel = 0;
/* Initialize.*/
textp = param_p;
textc = param_c;
arg [arg_c++] = textp;
for(;;) {
TICK("t_1aarg1");
/* Make sure there is room for one more. */
if (textc >= MAX_RTEXT - 2) {
goto toolong;
}
switch (ch) {
case END_FILE:
goto runon;
case '\n':
/* Convert to one blank. */
sysnext();
do_nl();
*textp++ = ' ';
textc++;
continue;
case '\r':
sysnext();
continue;
case '\\':
sysnext();
if (skip_crlf()) {
*textp++ = ' ';
textc++;
do_nl();
}
else {
*textp++ = '\\';
textc++;
}
continue;
case ',':
if (plevel == 0) {
goto end_arg;
}
else {
*textp++ = ch;
textc++;
sysnext();
/*
Allow raw newlines only at the start
or end of arguments.
*/
/* comment out -----
skip_ws();
if (ch == '\r') {
sysnext();
}
if (ch == '\n') {
do_nl();
sysnext();
}
----- end comment out */
}
continue;
case ')':
if (plevel == 0) {
goto end_arg;
}
else {
plevel--;
*textp++ = ch;
textc++;
sysnext();
continue;
}
case '(':
plevel++;
*textp++ = ch;
textc++;
sysnext();
continue;
case '"':
case '\'':
delim = ch;
*textp++ = delim;
textc++;
t_length = 0;
t_string(textp);
if (textc + t_length + 1 >= MAX_RTEXT1) {
goto toolong;
}
textp += t_length;
textc += t_length;
*textp++ = delim;
textc++;
continue;
case '/':
sysnext();
if (ch == '*') {
sysnext();
t_comment();
/* Change a comment into one blank. */
*textp++ = ' ';
textc++;
}
else {
*textp++ = '/';
textc++;
}
continue;
default:
if (isid1(ch)) {
t_id(textp);
if (textc + t_length >= MAX_RTEXT) {
goto toolong;
}
textp += t_length;
textc++;
}
else {
*textp++ = ch;
textc++;
sysnext();
}
}
}
end_arg:
/* Finish off the argument. */
*textp++ = '\0';
textc++;
/* Update the globals. */
param_p = textp;
param_c = textc;
TRACE("t_1aarg",
printf("t_1aarg returns arg[%d] = <%s>\n", arg_c - 1, arg[arg_c-1]));
TRACE("t_1aarg", printf("t_1aarg: ch = %c\n", ch));
return;
runon:
if (call_start != t_line) {
conv2s(call_start, linebuf);
str_cpy(buffer, "Runon macro call at line ");
str_cat(buffer, linebuf);
}
else {
str_cpy(buffer, "Runon macro call");
}
str_cat(buffer, "--last arg set to null.");
t_error(buffer);
arg [arg_c - 1] = NULL;
return;
toolong:
conv2s(call_start, linebuf);
str_cpy(buffer, "Macro arg starting at line ");
str_cat(buffer, linebuf);
str_cat(buffer, " is too long!");
fatal(buffer);
}